Skip to content

[Inference API] Add Tencent Cloud AI Service to Inference API#152829

Open
boicehuang wants to merge 36 commits into
elastic:mainfrom
boicehuang:tencentcloud_inference
Open

[Inference API] Add Tencent Cloud AI Service to Inference API#152829
boicehuang wants to merge 36 commits into
elastic:mainfrom
boicehuang:tencentcloud_inference

Conversation

@boicehuang

Copy link
Copy Markdown
Contributor

Description

Tencent Cloud provides an OpenAI-compatible AI Gateway that unifies access to a
family of Chinese and multilingual models used with Tencent Cloud
Elasticsearch. It exposes three endpoints, all authenticated via
Authorization: Bearer sk-<api-key>:

Endpoint Task type Sample supported models
POST /v1/embeddings text_embedding bge-base-zh-v1.5, bge-large-zh-v1.5, bge-m3, Conan-embedding-v1, Qwen3-Embedding-0.6B, …
POST /v1/chat/completions chat_completion / completion deepseek-v3, deepseek-r1
POST /v1/rerank rerank bge-reranker-large, bge-reranker-v2-m3

@elasticsearchmachine elasticsearchmachine added needs:triage Requires assignment of a team area label v9.5.0 external-contributor Pull request authored by a developer outside the Elasticsearch team labels Jul 3, 2026
@dimitris-athanasiou dimitris-athanasiou added :SearchOrg/Inference Label for the Search Inference team and removed needs:triage Requires assignment of a team area label labels Jul 7, 2026
@elasticsearchmachine

Copy link
Copy Markdown
Collaborator

Pinging @elastic/search-inference-team (Team:Search - Inference)

# Conflicts:
#	x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceNamedWriteablesProvider.java
@jonathan-buttner

Copy link
Copy Markdown
Contributor

@elasticmachine test this please

@jonathan-buttner

Copy link
Copy Markdown
Contributor

@boicehuang looks like compiling is failing with this:

Execution failed for task ':x-pack:plugin:inference:compileTestJava' (registered by plugin class 'org.gradle.api.plugins.JavaBasePlugin').
> Compilation failed; see the compiler output below.
/dev/shm/bk/bk-agent-prod-gcp-1783528152986749611/elastic/elasticsearch-pull-request/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/tencentcloud/TencentCloudServiceTests.java:284: error: unreported exception URISyntaxException; must be caught or declared to be thrown
            webServer.getUri(null),

If you could take a look that'd be great.

@jonathan-buttner

Copy link
Copy Markdown
Contributor

Hey @boicehuang looks like there are some failing tests:


2026-07-09 10:21:25 EDT | Tests with failures:
-- | --
  | 2026-07-09 10:21:25 EDT | - org.elasticsearch.xpack.inference.services.tencentcloud.completion.TencentCloudChatCompletionModelTests.testUri_UsesDefaultWhenNoOverride
  | 2026-07-09 10:21:25 EDT | - org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests.testParseRequestConfig_ChatCompletion_UsesDefaultUrl
  | 2026-07-09 10:21:25 EDT | - org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests.testSupportedStreamingTasks
  | 2026-07-09 10:21:25 EDT | - org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests.testParseRequestConfig_TextEmbedding
  | 2026-07-09 10:21:25 EDT | - org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests.testUpdateModelWithEmbeddingDetails_NullSimilarityInOriginalModel_UsesDefaultSimilarity
  | 2026-07-09 10:21:25 EDT | - org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests.testParsePersistedConfig_ChatCompletion
  | 2026-07-09 10:21:25 EDT | - org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests.testRerankersHaveWindowSize
  | 2026-07-09 10:21:25 EDT | - org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests.testChunkedInfer_UnsupportedForNonEmbeddingModel
  | 2026-07-09 10:21:25 EDT | - org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests.testUpdateModelWithEmbeddingDetails_NonNullSimilarityInOriginalModel_KeepsSimilarity
  | 2026-07-09 10:21:25 EDT | - org.elasticsearch.xpack.inference.services.tencentcloud.rerank.TencentCloudRerankModelTests.testUri_UsesDefaultWhenNoOverride
  | 2026-07-09 10:21:25 EDT | - org.elasticsearch.xpack.inference.services.tencentcloud.embeddings.TencentCloudEmbeddingsModelTests.testUri_UsesDefaultWhenNoOverride
  | 2026-07-09 10:21:25 EDT | - org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudCommonServiceSettingsTests.testFromMap_WithUrlAndRateLimit_Success


Here's an example of how to run one of them:

./gradlew ":x-pack:plugin:inference:test" --tests "org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudCommonServiceSettingsTests.testFromMap_WithUrlAndRateLimit_Success" -Dtests.seed=90748E56641DA02A -Dtests.locale=vun-Latn-TZ -Dtests.timezone=Pacific/Chuuk -Druntime.java=26

If you could take a look at fixing them that'd be great.

@boicehuang

boicehuang commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

@jonathan-buttner all tests are passed

@jonathan-buttner

Copy link
Copy Markdown
Contributor

@boicehuang could you take a look at this failure:


2026-07-13 15:04:46 EDT | > Task :x-pack:plugin:inference:test
-- | --
  | 2026-07-13 15:04:46 EDT |  
  | 2026-07-13 15:04:46 EDT | REPRODUCE WITH: ./gradlew ":x-pack:plugin:inference:test" --tests "org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests.testChunkedInfer_UnsupportedForNonEmbeddingModel" -Dtests.seed=E572E0D8D668FB3F -Dtests.locale=ksh -Dtests.timezone=Africa/Ceuta -Druntime.java=26
  | 2026-07-13 15:04:46 EDT |  
  | 2026-07-13 15:04:46 EDT | TencentCloudServiceTests > testChunkedInfer_UnsupportedForNonEmbeddingModel FAILED
  | 2026-07-13 15:04:46 EDT | junit.framework.AssertionFailedError: Unexpected exception type, expected ElasticsearchStatusException but got java.lang.NullPointerException: Cannot invoke "org.elasticsearch.inference.ModelConfigurations.getService()" because the return value of "org.elasticsearch.inference.Model.getConfigurations()" is null
  | 2026-07-13 15:04:46 EDT | at __randomizedtesting.SeedInfo.seed([E572E0D8D668FB3F:7EEBC37BE4203634]:0)
  | 2026-07-13 15:04:46 EDT | at app//org.apache.lucene.tests.util.LuceneTestCase.expectThrows(LuceneTestCase.java:2934)
  | 2026-07-13 15:04:46 EDT | at app//org.apache.lucene.tests.util.LuceneTestCase.expectThrows(LuceneTestCase.java:2915)
  | 2026-07-13 15:04:46 EDT | at app//org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests.testChunkedInfer_UnsupportedForNonEmbeddingModel(TencentCloudServiceTests.java:241)
  | 2026-07-13 15:04:46 EDT |  
  | 2026-07-13 15:04:46 EDT | Caused by:
  | 2026-07-13 15:04:46 EDT | java.lang.NullPointerException: Cannot invoke "org.elasticsearch.inference.ModelConfigurations.getService()" because the return value of "org.elasticsearch.inference.Model.getConfigurations()" is null
  | 2026-07-13 15:04:46 EDT | at org.elasticsearch.xpack.inference.services.ServiceUtils.createInvalidModelException(ServiceUtils.java:974)
  | 2026-07-13 15:04:46 EDT | at org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudService.doChunkedInfer(TencentCloudService.java:186)
  | 2026-07-13 15:04:46 EDT | at org.elasticsearch.xpack.inference.services.SenderService.chunkedInfer(SenderService.java:366)
  | 2026-07-13 15:04:46 EDT | at org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests.testChunkedInfer_UnsupportedForNonEmbeddingModel(TencentCloudServiceTests.java:240)
  | 2026-07-13 15:04:46 EDT |  
  | 2026-07-13 15:04:46 EDT |  
  | 2026-07-13 15:04:46 EDT | Suite: Test class org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests
  | 2026-07-13 15:04:46 EDT | 2> Jul 13, 2026 7:04:42 PM org.apache.lucene.internal.vectorization.PanamaVectorizationProvider <init>
  | 2026-07-13 15:04:46 EDT | 2> INFO: Java vector incubator API enabled; uses preferredBitSize=512; FMA enabled
  | 2026-07-13 15:04:46 EDT | 1> [2026-07-13T19:04:42,706][INFO ][o.e.c.u.FeatureFlag      ][suite] The current build is a snapshot, feature flag [field_info_caching_directory] is enabled
  | 2026-07-13 15:04:46 EDT | 1> [2026-07-13T19:04:43,011][INFO ][o.e.x.i.s.t.TencentCloudServiceTests][testChunkedInfer_UnsupportedForNonEmbeddingModel] before test
  | 2026-07-13 15:04:46 EDT | 1> [2026-07-13T19:04:43,075][INFO ][o.e.t.h.MockWebServer    ][testChunkedInfer_UnsupportedForNonEmbeddingModel] bound HTTP mock server to [127.0.0.1:33601]
  | 2026-07-13 15:04:46 EDT | 1> [2026-07-13T19:04:43,429][INFO ][o.e.c.u.FeatureFlag      ][testChunkedInfer_UnsupportedForNonEmbeddingModel] The current build is a snapshot, feature flag [batch_indexing] is enabled
  | 2026-07-13 15:04:46 EDT | 1> [2026-07-13T19:04:43,524][INFO ][o.e.c.u.FeatureFlag      ][testChunkedInfer_UnsupportedForNonEmbeddingModel] The current build is a snapshot, feature flag [chunked_fetch_phase_enabled] is enabled
  | 2026-07-13 15:04:46 EDT | 1> [2026-07-13T19:04:43,524][INFO ][o.e.c.u.FeatureFlag      ][testChunkedInfer_UnsupportedForNonEmbeddingModel] The current build is a snapshot, feature flag [pit_relocation_feature] is enabled
  | 2026-07-13 15:04:46 EDT | 1> [2026-07-13T19:04:43,708][INFO ][o.e.c.u.FeatureFlag      ][testChunkedInfer_UnsupportedForNonEmbeddingModel] The current build is a snapshot, feature flag [dlm_searchable_snapshots] is enabled
  | 2026-07-13 15:04:46 EDT | 1> [2026-07-13T19:04:43,764][INFO ][o.e.c.u.FeatureFlag      ][testChunkedInfer_UnsupportedForNonEmbeddingModel] The current build is a snapshot, feature flag [write_load_decider] is enabled
  | 2026-07-13 15:04:46 EDT | 1> [2026-07-13T19:04:44,627][INFO ][o.e.c.u.FeatureFlag      ][testChunkedInfer_UnsupportedForNonEmbeddingModel] The current build is a snapshot, feature flag [slice_indexing] is enabled
  | 2026-07-13 15:04:46 EDT | 1> [2026-07-13T19:04:44,658][INFO ][o.e.x.i.s.t.TencentCloudServiceTests][testChunkedInfer_UnsupportedForNonEmbeddingModel] after test
  | 2026-07-13 15:04:46 EDT | 2> REPRODUCE WITH: ./gradlew ":x-pack:plugin:inference:test" --tests "org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests.testChunkedInfer_UnsupportedForNonEmbeddingModel" -Dtests.seed=E572E0D8D668FB3F -Dtests.locale=ksh -Dtests.timezone=Africa/Ceuta -Druntime.java=26
  | 2026-07-13 15:04:46 EDT | 2> junit.framework.AssertionFailedError: Unexpected exception type, expected ElasticsearchStatusException but got java.lang.NullPointerException: Cannot invoke "org.elasticsearch.inference.ModelConfigurations.getService()" because the return value of "org.elasticsearch.inference.Model.getConfigurations()" is null
  | 2026-07-13 15:04:46 EDT | at __randomizedtesting.SeedInfo.seed([E572E0D8D668FB3F:7EEBC37BE4203634]:0)
  | 2026-07-13 15:04:46 EDT | at app//org.apache.lucene.tests.util.LuceneTestCase.expectThrows(LuceneTestCase.java:2934)
  | 2026-07-13 15:04:46 EDT | at app//org.apache.lucene.tests.util.LuceneTestCase.expectThrows(LuceneTestCase.java:2915)
  | 2026-07-13 15:04:46 EDT | at app//org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests.testChunkedInfer_UnsupportedForNonEmbeddingModel(TencentCloudServiceTests.java:241)
  | 2026-07-13 15:04:46 EDT |  
  | 2026-07-13 15:04:46 EDT | Caused by:
  | 2026-07-13 15:04:46 EDT | java.lang.NullPointerException: Cannot invoke "org.elasticsearch.inference.ModelConfigurations.getService()" because the return value of "org.elasticsearch.inference.Model.getConfigurations()" is null
  | 2026-07-13 15:04:46 EDT | at org.elasticsearch.xpack.inference.services.ServiceUtils.createInvalidModelException(ServiceUtils.java:974)
  | 2026-07-13 15:04:46 EDT | at org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudService.doChunkedInfer(TencentCloudService.java:186)
  | 2026-07-13 15:04:46 EDT | at org.elasticsearch.xpack.inference.services.SenderService.chunkedInfer(SenderService.java:366)
  | 2026-07-13 15:04:46 EDT | at org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests.testChunkedInfer_UnsupportedForNonEmbeddingModel(TencentCloudServiceTests.java:240)
  | 2026-07-13 15:04:46 EDT | 2> NOTE: leaving temporary files on disk at: /dev/shm/bk/bk-agent-prod-gcp-1783969171772333417/elastic/elasticsearch-pull-request/x-pack/plugin/inference/build/testrun/test/temp/org.elasticsearch.xpack.inference.services.tencentcloud.TencentCloudServiceTests_E572E0D8D668FB3F-001
  | 2026-07-13 15:04:46 EDT | 2> NOTE: test params are: codec=Asserting(Lucene104): {}, docValues:{}, maxPointsInLeafNode=442, maxMBSortInHeap=7.261385058417083, sim=Asserting(RandomSimilarity(queryNorm=false): {}), locale=ksh, timezone=Africa/Ceuta
  | 2026-07-13 15:04:46 EDT | 2> NOTE: Linux 6.17.0-1020-gcp amd64/Oracle Corporation 26.0.1 (64-bit)/cpus=16,threads=1,free=381901568,total=536870912
  | 2026-07-13 15:04:46 EDT | 2> NOTE: All tests run in this JVM: [TencentCloudServiceTests]


@boicehuang

Copy link
Copy Markdown
Contributor Author

@jonathan-buttner i have fix it. please retry the cli test.

@jonathan-buttner jonathan-buttner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the tests, I left a few suggestions.

Comment thread docs/changelog/152829.yaml Outdated
@@ -0,0 +1,5 @@
pr: 152829
summary: Add Tencent Cloud inference service
area: Machine Learning

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
area: Machine Learning
area: Inference

*/
@Override
public ExecutableAction accept(TencentCloudActionVisitor visitor, Map<String, Object> taskSettings) {
throw new UnsupportedOperationException(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ESQL leverages the completion task type which typically uses the class like this one via the visitor pattern. We should implement the completion functionality so ESQL can leverage this integration.

Another option instead of the visitor pattern is to use a strategy like ElasticInferenceService:
https://github.com/elastic/elasticsearch/blob/main/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/action/ElasticInferenceServiceActionCreator.java#L43

https://github.com/elastic/elasticsearch/blob/main/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceService.java#L307-L311

* Dispatches TencentCloud AI Gateway chat completion requests. TencentCloud is OpenAI-compatible so the OpenAI
* response handlers are reused.
*/
public class TencentCloudChatCompletionRequestManager extends BaseRequestManager {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're trying to move away from additional *RequestManager classes. Here's how OpenAI does it: https://github.com/elastic/elasticsearch/blob/main/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/openai/OpenAiService.java#L245-L251

Can we follow that pattern for doUnifiedCompletionInfer or follow what the ElasticInferenceService does and then remove this class?

import java.util.Map;
import java.util.Objects;

public class TencentCloudChatCompletionServiceSettings extends FilteredXContentObject

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're refactoring how we parse settings. Let's move to the new way which is to use an ObjectParser. Take a look at https://github.com/elastic/elasticsearch/blob/main/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/llama/embeddings/LlamaEmbeddingsServiceSettings.java for an example.

int initialValidationErrorCount = validationException.validationErrors().size();

var modelId = extractRequiredString(map, ServiceFields.MODEL_ID, ModelConfigurations.SERVICE_SETTINGS, validationException);
var uri = extractOptionalUri(map, ServiceFields.URL, validationException);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can users change the URL (like can they spin up deployments)? Or will this always point to the same place? If it's the same place then let's hard code it instead like JinaAI: https://github.com/elastic/elasticsearch/blob/main/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/jinaai/rerank/JinaAIRerankModel.java#L31-L33


@Override
public TransportVersion getMinimalSupportedVersion() {
return TransportVersion.minimumCompatible();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Batch limit for embedding chunking. TencentCloud AI Gateway does not document a hard cap; use a conservative value.
private static final int EMBEDDING_MAX_BATCH_SIZE = 32;

private static final EnumSet<TaskType> SUPPORTED_TASK_TYPES = EnumSet.of(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add TaskType.COMPLETION


@Override
public int rerankerWindowSize(String modelId) {
// BGE reranker models (bge-reranker-large, bge-reranker-v2-m3) support up to 512-token inputs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these the only rerank models that are supported?


// Empty default task settings
namedWriteables.add(new NamedWriteableRegistry.Entry(TaskSettings.class, EmptyTaskSettings.NAME, EmptyTaskSettings::new));
namedWriteables.add(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably a merge conflict resolution issue, let's add these changes back and we need to follow a similar pattern as the other services in this file to add the named writeables that are introduced in this PR.

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class TencentCloudServiceTests extends InferenceServiceTestCase {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…on, COMPLETION support, visitor pattern

- Register all TencentCloud ServiceSettings/TaskSettings in InferenceNamedWriteablesProvider
- Add dedicated TransportVersion (ml_inference_tencentcloud_added, ID 9479000)
- Add TaskType.COMPLETION support for ESQL
- Remove TencentCloudChatCompletionRequestManager, route completion via visitor pattern
- Fix changelog area: Machine Learning -> Inference
- Update tests for streaming tasks and COMPLETION
Replace manual map field extraction with declarative ObjectParser parsing
in all TencentCloud service settings classes, following the pattern set by
LlamaEmbeddingsServiceSettings. Common fields (model_id, url, rate_limit,
api_key) are declared once via TencentCloudCommonServiceSettings.declareCommonFields()
and reused by each task-specific settings parser.

- TencentCloudCommonServiceSettings: add declareCommonFields(), CommonSettingsBuilder interface, ObjectParser-based fromMap()
- TencentCloudEmbeddingsServiceSettings: use ObjectParser for common + embeddings-specific fields (similarity, dimensions, max_input_tokens)
- TencentCloudChatCompletionServiceSettings: use ObjectParser with chat-completion default rate limit
- TencentCloudRerankServiceSettings: use ObjectParser for common fields
…loud service

Users cannot bring their own TencentCloud deployment, but they may need to
target different regional endpoints. Replace the free-form 'url' setting with
a 'region' setting (default 'bj'). The endpoint URL is always constructed as
https://{region}.aisearch.tencentelasticsearch.com/v1/<task-path>.

- TencentCloudUtils: add buildHost(region), buildUri(region, path...) helpers
- TencentCloudCommonServiceSettings: replace @nullable URI uri with String region
- All model classes: resolveUri() now builds URI from region, no url override
- TencentCloudService: add usesParserForServiceSettings() override, replace
  URL config with region config
- Remove URL from declareCommonFields (consumed silently for backward compat)
- Tests: rewrite URL-related tests to use region; remove WebServer-dependent
  integration tests (endpoint is now hardcoded to *.tencentelasticsearch.com)
…oud service

- Update rerankerWindowSize() comment: rerank supports multiple models
  (not a fixed list), model IDs are passed through to the gateway
- Update MODEL_ID config description to mention the full list is in
  gateway documentation
- Delete TencentCloudEndpointUtils.java (unused after URL→region migration)
- Make COMPLETION_ERROR_PREFIX and USER_ROLE private in ActionCreator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

>enhancement external-contributor Pull request authored by a developer outside the Elasticsearch team :SearchOrg/Inference Label for the Search Inference team Team:Search - Inference v9.6.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants